This depends on the order in which the contract performs operations. This vulnerability exists because the withdraw function can be called multiple times before it completes execution.
In the attack code, the attack() function is called and a withdrawal is initiated. The msg.sender.call.value(withdrawAmount)(""); line is executed before the balance is subtracted. This allows code to run after the balance check but before the balance is updated. This triggers the fallback function shown below, which recursively drains funds from the contract and transfers them to the attacker’s address.